@using Microsoft.AspNetCore.Blazor.Http
@inject HttpClient Http
@inject NavigationManager NavigationManager

<h2>HTTP Request Tester</h2>

<p>The default values of the following form POST (add a Todo item) to the web API created in the <a href="https://docs.microsoft.com/aspnet/core/tutorials/first-web-api">Tutorial: Create a web API with ASP.NET Core MVC</a> topic with:</p>

<ul>
    <li>An endpoint URI of <code>https://localhost:10000/api/TodoItems</code>.</li>
    <li>The <code>Content-Type</code> header set to <code>application/json</code>, which describes the payload of a POST/PUT request to the service.</li>
</ul>

<p>Add the following CORS middleware configuration to the web API's <code>Startup.Configure</code> method before it calls <code>UseMvc</code>:</p>

<pre><code>app.UseCors(policy =>
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
    .AllowAnyMethod()
    .WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization)
    .AllowCredentials());</code></pre>

<p>Adjust the domains and ports of <code>WithOrigins</code> as needed for the Blazor app.</p>

<p>The web API is configured for CORS to permit authorization cookies/headers and requests from client code, but the web API as created by the tutorial doesn't actually authorize requests. See the <a href="https://docs.microsoft.com/aspnet/core/security/">ASP.NET Core Security and Identity articles</a> for implementation guidance.</p>

<p>
    <div>URI:</div>
    <input id="request-uri" @bind="_uri" size="120" />
</p>

<p>
    <div>Method:</div>
    <select id="request-method" @bind="_method">
        <option value="GET">GET</option>
        <option value="POST" selected>POST</option>
        <option value="PUT">PUT</option>
        <option value="DELETE">DELETE</option>
    </select>
</p>

<p>
    <div>Request body:</div>
    <textarea id="request-body" @bind="_requestBody"></textarea>
</p>

<p>
    <div>Request headers:</div>
    @foreach (var header in _requestHeaders)
    {
        <div class="header-entry">
            Name: <input @bind="header.Name" />
            Value: <input @bind="header.Value" />
            <button class="btn btn-danger" @onclick="@(e => RemoveHeader(header))">remove</button>
        </div>
    }
    <button class="btn btn-primary" id="add-header" @onclick="@AddHeader">Add</button>
</p>

<button class="btn btn-success" id="send-request" @onclick="@DoRequest">Request</button>

@if (_responseStatusCode.HasValue)
{
    <h2>Response</h2>
    <p><div>Status:</div><span id="response-status">@_responseStatusCode</span></p>
    <p><div>Body:</div><textarea id="response-body" readonly>@_responseBody</textarea></p>
    <p><div>Headers:</div><textarea id="response-headers" readonly>@_responseHeaders</textarea></p>
}

@code {
    private string _uri = "https://localhost:10000/api/TodoItems";
    private string _method = "POST";
    private string _requestBody = @"{""name"":""A New Todo Item"",""isComplete"":false}";
    private List<RequestHeader> _requestHeaders = new List<RequestHeader>()
    {
        new RequestHeader() { Name = "Content-Type", Value = "application/json" },
        new RequestHeader() { Name = "Authorization", Value = "Bearer MHrHDcEfxjoYZgeFONFh7HgQ" }
    };
    private System.Net.HttpStatusCode? _responseStatusCode;
    private string _responseBody;
    private string _responseHeaders;

    private async void DoRequest()
    {
        _responseStatusCode = null;

        try
        {
            var requestMessage = new HttpRequestMessage()
            {
                Method = new HttpMethod(_method),
                RequestUri = new Uri(_uri),
                Content = string.IsNullOrEmpty(_requestBody) ? null : new StringContent(_requestBody)
            };

            foreach (var header in _requestHeaders)
            {
                // StringContent automatically adds its own Content-Type header with default value "text/plain"
                // If the developer is trying to specify a content type explicitly, we need to replace the default value,
                // rather than adding a second Content-Type header.
                if (header.Name.Equals("Content-Type", StringComparison.OrdinalIgnoreCase) && requestMessage.Content != null)
                {
                    requestMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(header.Value);
                    continue;
                }

                if (!requestMessage.Headers.TryAddWithoutValidation(header.Name, header.Value))
                {
                    requestMessage.Content?.Headers.TryAddWithoutValidation(header.Name, header.Value);
                }
            }

            var response = await Http.SendAsync(requestMessage);
            _responseStatusCode = response.StatusCode;
            _responseBody = await response.Content.ReadAsStringAsync();
            var allHeaders = response.Headers.Concat(response.Content?.Headers ?? Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>());
            _responseHeaders = string.Join(Environment.NewLine, allHeaders.Select(pair => $"{pair.Key}: {pair.Value.First()}").ToArray());
        }
        catch (Exception ex)
        {
            if (ex is AggregateException)
            {
                ex = ex.InnerException;
            }
            _responseStatusCode = System.Net.HttpStatusCode.SeeOther;
            _responseBody = ex.Message + Environment.NewLine + ex.StackTrace;
        }

        StateHasChanged();
    }

    private void AddHeader()
        => _requestHeaders.Add(new RequestHeader());

    private void RemoveHeader(RequestHeader header)
        => _requestHeaders.Remove(header);

    private class RequestHeader
    {
        public string Name { get; set; }
        public string Value { get; set; }
    }
}
